﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml.XPath;

using Microsoft.Office.InfoPath;
using InfoPathDebugger.UserControls;
using System.IO;
using System.Xml.Linq;
using System.Xml;

namespace InfoPathDebugger
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
            // Create a InternalStartup event handler to initialize the xml event handlers.
            InfoPathFormControl.InternalStartup += new Microsoft.Office.InfoPath.FormControl.EventHandler<EventArgs>(InfoPathFormControl_InternalStartup);
        }

        #region Form Control Event Handlers

        private void InfoPathFormControl_InternalStartup(object sender, EventArgs e)
        {
            FormControl formControl = (FormControl)sender;

            // Get the xml events collection.
            XmlEvents allXmlEvents = formControl.EventManager.XmlEvents;

            // Get the data source collection.
            DataSourceCollection allDatasources = formControl.XmlForm.DataSources;
            foreach (DataSource ds in allDatasources)
            {
                XPathNavigator navigator = ds.CreateNavigator();

                // Get xpaths of all data fields.
                List<String> allDataFieldXPath = XPathHelper.GetDatasourceLeafNodesXPath(navigator);
                foreach (String dfXPath in allDataFieldXPath)
                {
                    // Add data changed event handler to the sepcific data field.
                    allXmlEvents[dfXPath, ds.Name].Changed += new XmlChangedEventHandler(DataField_Changed);
                }
            }

            List<String> roleList = getRolesFromInfoPathForm(formControl.XmlForm.Template.Manifest);

            // Initialize user role dropdown list.
            foreach (String role in roleList)
            {
                cbUserRole.Items.Add(role);
            }

            // Select current user role.
            cbUserRole.SelectedItem = formControl.XmlForm.UserRole;

            executeOnLoadRules();

            changeValueCToolStripMenuItem.Enabled = true;

            saveAsMenuItem.Enabled = true;
        }

        #endregion

        private void executeOnLoadRules()
        {
            // Initialize a rule manager. Rule manager is used to execute rules
            // of InfoPath.
            RuleManager.RuleManager ruleMgr = new RuleManager.RuleManager();
            ruleMgr.MainDataSource = InfoPathFormControl.XmlForm.MainDataSource;
            ruleMgr.AllDataSource = InfoPathFormControl.XmlForm.DataSources;
            ruleMgr.DataConnections = InfoPathFormControl.XmlForm.DataConnections;
            ruleMgr.Role = InfoPathFormControl.XmlForm.UserRole;
            ruleMgr.AssignmentActionExecutor = new RuleManager.AssignmentActionExecutor();
            ruleMgr.QueryActionExecutor = new RuleManager.QueryActionExecutor();

            // Find the onload rule node.
            XPathNavigator onLoadRuleSetNav = findOnLoadRuleSetNode(InfoPathFormControl.XmlForm.Template.Manifest);
            if (onLoadRuleSetNav.MoveToChild(XPathNodeType.Element))
            {
                // Execute all onload rules.
                do
                {
                    // Catch the exception to prevent stopping rule execution.
                    try
                    {
                        ruleMgr.ExecuteRule(onLoadRuleSetNav);
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }

                } while (onLoadRuleSetNav.MoveToNext(XPathNodeType.Element));
            }
        }

        /// <summary>
        /// Find the onload rule set node.
        /// </summary>
        /// <param name="manifestNavigator">A navigator point to the manifest data file.</param>
        /// <returns>A navigator point to the onload rule set.</returns>
        private XPathNavigator findOnLoadRuleSetNode(XPathNavigator manifestNavigator)
        {
            XPathNavigator onLoadRuleSetNav = null;
            IDictionary<String, String> nsDict = XPathHelper.RetrieveNamespaces(manifestNavigator);
            XmlNamespaceManager nsManager = new XmlNamespaceManager(manifestNavigator.NameTable);
            nsManager.AddNamespace("xsf", nsDict["xsf"]);

            // Select xsf:onload first.
            XPathNavigator onLoadNav = manifestNavigator.SelectSingleNode("//xsf:onLoad", nsManager);
            if (onLoadNav.MoveToChild(XPathNodeType.Element))
            {
                String onLoadRuleSet = onLoadNav.GetAttribute("ruleSet", String.Empty);

                // Select all xsf:ruleSet.
                XPathNodeIterator iter = manifestNavigator.Select("//xsf:xDocumentClass/xsf:ruleSets/xsf:ruleSet", nsManager);
                while (iter.MoveNext())
                {
                    if (iter.Current.GetAttribute("name", String.Empty).Equals(onLoadRuleSet))
                    {
                        onLoadRuleSetNav = iter.Current;
                        break;
                    }
                }
            }

            return onLoadRuleSetNav;
        }

        #region UI Event Handlers

        private void DataField_Changed(object sender, XmlEventArgs e)
        {
            // Get the xpath of the changed field.
            String changedFieldXPath = XPathHelper.CreateXPathString(e.Site);

            // Create data item of the changed field.
            FieldChangedListViewDataItem dataItem = createFieldChangedListViewDataItem(e.Site.Name, changedFieldXPath, e.OldValue, e.NewValue, e.Operation.ToString(), DateTime.Now);

            // Update list view which represents the changed fields.
            monitorControl.UpdateFieldsChangedListView(dataItem);

            // Update watch list view
            monitorControl.UpdateWatchListView(dataItem);
        }

        private void openInfoPathFormToolStripMenuItem_Click(object sender, EventArgs e)
        {
            popupOpenFileDialog(new FormOpenProvider.NormalFormOpenProvider());
        }

        private void toolStripMenuForceOpenInfoPathForm_Click(object sender, EventArgs e)
        {
            popupOpenFileDialog(new FormOpenProvider.ForceFormOpenProvider());
        }

        private void toolStripMenuOpenFormData_Click(object sender, EventArgs e)
        {
            popupOpenFileDialog(new FormOpenProvider.FormDataOpenProvider(), "InfoPath files (*.xml)|*.xml|All files (*.*)|*.*");
        }

        private void exitXToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            AboutForm aboutForm = new AboutForm();
            aboutForm.ShowDialog();
        }

        private void cbUserRole_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.InfoPathFormControl.XmlForm.UserRole = cbUserRole.SelectedItem.ToString();
            this.InfoPathFormControl.XmlForm.CurrentView.ForceUpdate();
        }

        private void changeValueCToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (InfoPathFormControl.XmlForm != null)
            {
                ChangeValueForm cvForm = new ChangeValueForm(InfoPathFormControl.XmlForm.DataSources);
                cvForm.ShowDialog(this);
            }
        }

        #endregion

        #region Helper Functions

        private void loadInfoPathForm(String formPath, FormOpenProvider.IFormOpenProvider formOpenProvider)
        {
            try
            {
                // Open a new InfoPath form.
                formOpenProvider.Open(InfoPathFormControl, formPath);
            }
            catch (IOException ex)
            {
                if (ex.Message.Contains("The process cannot access the file"))
                {
                    String newMessage = "Please restart InfoPath debugger.";
                    newMessage = ex.Message + System.Environment.NewLine + System.Environment.NewLine + newMessage;
                    
                    MessageBox.Show(newMessage);
                }
            }
            catch (Exception ex)
            {
                String message = ex.Message;
                if (message.Contains("The signature on this form is not from a trusted publisher"))
                {
                    StringBuilder sb = new StringBuilder(message);
                    sb.Append("\r\n");
                    sb.Append("\r\n");
                    sb.Append("Follow the steps to fix this issue:\r\n\r\n");
                    sb.Append("1. Open the form in design mode.\r\n");
                    sb.Append("2. Tools -> Form Options -> Security and Trust -> Create Certificate.\r\n");
                    sb.Append("3. Close the form. Double click the form to open it. There'll be a dialog to say:\r\n");
                    sb.Append("'Warning: This publisher has not been authenticated and therefore could be imitated.' Click\r\n");
                    sb.Append("'Trust all documents from this publisher'.\r\n");
                    sb.Append("4. Use InfoPath Debugger to open the form again.");

                    message = sb.ToString();
                }

                MessageBox.Show(message);
            }

            if (InfoPathFormControl.XmlForm != null)
            {
                // Pass the data source collection to monitor control.
                monitorControl.DataSourceCollection = InfoPathFormControl.XmlForm.DataSources;
            }
        }

        private void clearMonitorControl()
        {
            monitorControl.Clear();
        }

        private FieldChangedListViewDataItem createFieldChangedListViewDataItem(String name,
            String path,
            String oldValue,
            String newValue,
            String operation,
            DateTime time)
        {
            FieldChangedListViewDataItem dataItem = new FieldChangedListViewDataItem()
            {
                Name = name,
                Path = path,
                OldValue = oldValue,
                NewValue = newValue,
                Operation = operation,
                Time = time.ToLongTimeString()
            };

            return dataItem;
        }

        /// <summary>
        /// Popup the open file dialog to select the InfoPath form.
        /// </summary>
        /// <param name="openProvider">Specifies a provider to open the InfoPath form. Different providers open InfoPath form in different ways.</param>
        private void popupOpenFileDialog(FormOpenProvider.IFormOpenProvider openProvider)
        {
            popupOpenFileDialog(openProvider, "InfoPath files (*.xsn)|*.xsn|All files (*.*)|*.*");
        }

        private void popupOpenFileDialog(FormOpenProvider.IFormOpenProvider openProvider, String filterString)
        {
            if (InfoPathFormControl.XmlForm != null)
            {
                InfoPathFormControl.XmlForm.Close();
                GC.Collect();
            }

            OpenFileDialog openFileDlg = new OpenFileDialog();
            openFileDlg.Filter = filterString;
            openFileDlg.Multiselect = false;

            if (DialogResult.OK == openFileDlg.ShowDialog())
            {
                clearMonitorControl();
                loadInfoPathForm(openFileDlg.FileName, openProvider);
                cbUserRole.Enabled = true;
            }
        }

        private List<String> getRolesFromInfoPathForm(XPathNavigator manifestNav)
        {
            XPathNavigator nav = manifestNav.Clone();
            List<String> roleList = new List<string>();

            // Get xsf namespace.
            nav.MoveToFollowing(XPathNodeType.Element);
            IDictionary<String, String> nsDict = nav.GetNamespacesInScope(System.Xml.XmlNamespaceScope.All);
            XNamespace xsfNS = XNamespace.Get(nsDict["xsf"]);

            // Navigate back to first node.
            nav.MoveTo(manifestNav);

            // Retrieve roles from <xsf:roles /> element.
            Boolean hasRoles = nav.MoveToChild("roles", xsfNS.NamespaceName);
            if (hasRoles)
            {
                if (nav.MoveToChild(XPathNodeType.Element))
                {
                    do
                    {
                        String roleName = nav.GetAttribute("name", String.Empty);
                        roleList.Add(roleName);
                    } while (nav.MoveToNext(XPathNodeType.Element) && nav.LocalName.Equals("role"));
                }
            }

            return roleList;
        }

        #endregion

        //private void saveMenuItem_Click(object sender, EventArgs e)
        //{
        //    if (InfoPathFormControl.XmlForm != null)
        //    {
        //        try
        //        {
        //            InfoPathFormControl.XmlForm.Save();
        //            MessageBox.Show(String.Format("Saved file to {0}.", InfoPathFormControl.XmlForm.Uri), "Save", MessageBoxButtons.OK, MessageBoxIcon.Information);
        //        }
        //        catch (Exception ex)
        //        {
        //            MessageBox.Show(ex.Message);
        //        }
        //    }
        //}

        private void saveAsMenuItem_Click(object sender, EventArgs e)
        {
            if (InfoPathFormControl.XmlForm != null)
            {
                String uri = InfoPathFormControl.XmlForm.Uri;

                Int32 lastSlashIndex = uri.LastIndexOf("\\");

                if (lastSlashIndex != -1)
                {
                    uri.Remove(uri.LastIndexOf("\\"));
                }

                SaveFileDialog sfDlg = new SaveFileDialog();
                sfDlg.InitialDirectory = uri;
                sfDlg.Filter = "InfoPath Data File (*.xml)|*.xml";

                if (DialogResult.OK == sfDlg.ShowDialog())
                {
                    try
                    {
                        InfoPathFormControl.XmlForm.SaveAs(sfDlg.FileName);
                        MessageBox.Show("Save completed.", "Save", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                }
            }
        }
    }
}
